/**
 * Copyright (c) 2009, Robert Schuster
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 *
 * - Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer in the documentation and/or other
 *   materials provided with the distribution.
 * - Neither the name of Robert Schuster nor the names of his
 *   contributors may be used to endorse or promote products
 *   derived from this software without specific prior written
 *   permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _CLCALC_H
#define	_CLCALC_H

#include <stddef.h>

//All Functions should be callable from a c-Programm (or a fortran-Programm)
#ifdef __cplusplus
extern "C" {
#endif

    //Direction for the Arguments
    /**
     * This flag specifies that the memory object will be read and written by a kernel.
     */
    #define CL_MEM_READ_WRITE                           (1 << 0)
    /**
     * This flags specifies that the memory object will be written but not read by a kernel.
     * <p>Reading from a buffer or image object created with CL_MEM_WRITE_ONLY inside a kernel is undefined.
     */
    #define CL_MEM_WRITE_ONLY                           (1 << 1)
    /**
     * This flag specifies that the memory object is a read-only memory object when used inside a kernel.
     * <p>Writing to a buffer or image object created with CL_MEM_READ_ONLY inside a kernel is undefined.
     */
    #define CL_MEM_READ_ONLY                            (1 << 2)
    /**
     * This flag is valid only if host_ptr is not NULL. If specified, it indicates that the application wants the OpenCL implementation to use memory referenced by host_ptr as the storage bits for the memory object.
     * <p>OpenCL implementations are allowed to cache the buffer contents pointed to by host_ptr in device memory. This cached copy can be used when kernels are executed on a device.
     * <p>The result of OpenCL commands that operate on multiple buffer objects created with the same host_ptr or overlapping host regions is considered to be undefined.
     */
    #define CL_MEM_USE_HOST_PTR                         (1 << 3)
    /**
     * This flag specifies that the application wants the OpenCL implementation to allocate memory from host accessible memory.
     * <p>CL_MEM_ALLOC_HOST_PTR and CL_MEM_USE_HOST_PTR are mutually exclusive.
     */
    #define CL_MEM_ALLOC_HOST_PTR                       (1 << 4)
    /**
     * This flag is valid only if host_ptr is not NULL. If specified, it indicates that the application wants the OpenCL implementation to allocate memory for the memory object and copy the data from memory referenced by host_ptr.
     * <p>CL_MEM_COPY_HOST_PTR and CL_MEM_USE_HOST_PTR are mutually exclusive.
     * <p>CL_MEM_COPY_HOST_PTR can be used with CL_MEM_ALLOC_HOST_PTR to initialize the contents of the cl_mem object allocated using host-accessible (e.g. PCIe) memory.
     */
    #define CL_MEM_COPY_HOST_PTR                        (1 << 5)

    /*
     * Device type Contants
     */
    #define CLcalc_DEVICE_gpu0       0
    #define CLcalc_DEVICE_gpu1       1
    #define CLcalc_DEVICE_cpu0       2
    #define CLcalc_DEVICE_cpu1       3
    #define CLcalc_DEVICE_gpu_fast   4
    #define CLcalc_DEVICE_cpu_fast   5


    /*
     * The Default Executor
     */
    #define CLcalc_DEFAULT_EXECUTOR_ID  0

    /*
     * Init the CLcalc-Library.
     * You have to call this function befor you
     * are able to use the Libray. It sets up the Devices etc.
     * @return 0 = no error else error
     */
    int CLcalc_init();

    /*
     * You have to call this function to clean up the Memory of the GPU at the end
     * of your Programm
     * @return 0=no errors else error
     */
    int CLcalc_shutdown();

    /*
     * @return the Time in Milliseconds.
     */
    double CLcalc_getTimeInMillis();

    /*
     * Returns the Name of the Device or null if the Device is not available
     */
    const char* CLcalc_getDeviceName(int device_type);

    /*
     * Returns the Name of the Device or null if the Device is not available
     */
    int* CLcalc_getDeviceSpeed(int device_type);

    /*
     * In the case of an exception you can use this function to get the Error text
     */
    const char* CLcalc_getLastErrorText();

    /*
     * returns a pointer to the last Error-Object. You can't use the with C. (C++ Only)
     * In a C-Program use CLcalc_getLastErrorText()
     */
    void* CLcalc_getLastErrorObject();

    //--------------------------------------------------------------------------------------------------
    //Interface to the Executor-Objects
    //--------------------------------------------------------------------------------------------------

    /*
     * Create a new Executor-Object
     * @return a Executor-ID
     */
    int CLcalc_Executor_new(int device_type);

    /*
     * Add a Programm to execute, this directly builds a kernel. If the Program already exists
     * for this device(s), it is not build again.
     * @return a progID witch refers to the kernel, not to the compiled program.
     */
    int CLcalc_Executor_addProgFromSource(int ExecutorID, const char* source, const char* name);

    /*
     * Add an data to a Device
     */
    int CLcalc_Executor_addData(int ExecutorID, void* data, size_t data_size, int data_dir);

    /*
     * Read Data from a device
     */
    int CLcalc_Executor_getData(int ExecutorID, int DataID, void* data, size_t data_size);

    /*
     * release a used Momory-Object, that will cause the device to free the Memory
     * The Executor will call that function also inside of the shutdown function
     */
    int CLcalc_Executor_releaseData(int ExecutorID, int DataID);

    /**
     * The functions returns the Host-Pointer to the data-object
     * @param ExecutorID    the ID of the Executor Object
     * @param DataID        the ID of this data Object inside the Executor
     * @return              the pointer or NULL in case of an error. Use getLastError... to check that
     */
    void* CLcalc_Executor_getDataHostPtr(int ExecutorID, int DataID);

    /**
     * get the direction of the data object
     * @param ExecutorID    the ID of the Executor Object
     * @param DataID        the ID of this data Object inside the Executor
     * @return              CL_MEM_... the value used while creating the object
     */
    long CLcalc_Executor_getDataDir(int ExecutorID, int DataID);

    /**
     * Assign Data to a Program as an argument
     * @return 0 on success
     */
    int CLcalc_Executor_setArgument(int ExecutorID, int progID, int arg_pos, int DataID);

    /*
     * Assign Data to a Program as an argument
     * @return 0 on success
     */
    int CLcalc_Executor_setConstIntArgument(int ExecutorID, int progID, int arg_pos, int argument);

    /*
     * Execute the Kernel with the Index 0 to MaxIndex
     * @return 0 on success
     */
    int CLcalc_Executor_Execute(int ExecutorID, int progID, int MaxIndex);

    /*
     * Release everything that is attached to the executor, the ExecutorID is not longer valid
     */
    int CLcalc_Executor_shutdown(int ExecutorID);

    //--------------------------------------------------------------------------------------------------
    //Interface to the Build In Functions
    //--------------------------------------------------------------------------------------------------

    /**
     * Calculate the sum of each element of 2 arrays
     * @param ExecutorID    the ID of the Executor returned by CLcalc_Executor_new, or 0 for the default Executor
     * @param arg1          the 1st argument array
     * @param arg2          the 2nd argument array
     * @param result        the result array
     * @param length        the length of each dimension
     * @param dims          the number of dimension
     * @param basic_typ     the type of basic arithmetic operation to use
     */
    int CLcalc_Executor_BuildIn_basic(int ExecutorID, void* arg1, void* arg2, void* result, int* length, int dims, const char* fname, const char* tname);

    /**
     * Calculate the sum of each element of 2 arrays, no data tranfer is
     * will be done. The data must already be on the device.
     * @param ExecutorID    the ID of the Executor returned by CLcalc_Executor_new, or 0 for the default Executor
     * @param arg1ID        the ID of the 1st argument array
     * @param arg2ID        the ID of the 2nd argument array
     * @param resultID      the ID of the result array
     * @param length        the length of each dimension
     * @param dims          the number of dimension
     * @param basic_typ     the type of basic arithmetic operation to use
     */
    int CLcalc_Executor_BuildIn_basic_execOnly(int ExecutorID, int arg1ID, int arg2ID, int resultID, int* length, int dims, const char* fname, const char* tname);

    /**
     * Use one of the OpenCL intrinsic functions which have one argument
     * for example output[i] = sqrt(input[i])
     * @param exe       A Pointer to the Executor Object that should execute this function
     * @param input     the input array
     * @param output    the output array
     * @param length    the length of each dimension
     * @param dims      the number of dimensions
     * @param fname     the function to use, example: "sqrt"
     * @param tname     the type to use, example: "float"
     */
    int CLcalc_Executor_BuildIn_CLintrinsics1arg(int ExecutorID, void* input, void* output, int* length, int dims, const char* fname, const char* tname);

    /**
     * Use one of the OpenCL intrinsic functions which have one argument
     * for example output[i] = sqrt(input[i])
     * @param exe       A Pointer to the Executor Object that should execute this function
     * @param input     the input array
     * @param output    the output array
     * @param length    the length of each dimension
     * @param dims      the number of dimensions
     * @param fname     the function to use, example: "sqrt"
     * @param tname     the type to use, example: "float"
     */
    int CLcalc_Executor_BuildIn_CLintrinsics1arg_execOnly(int ExecutorID, int inputID, int outputID, int* length, int dims, const char* fname, const char* tname);

#ifdef __cplusplus
}
#endif

#endif	/* _CLCALC_H */

